#include <timer.h>
#include <irq.h>
#include <clk.h>
#include <errno.h>
#include <config.h>
#include <device.h>
#include <bitops.h>
#include <math64.h>
#include <limits.h>
#include <arch/io.h>
#include <arch/cpu/soc/irq.h>
#include <arch/cpu/soc/s3c2440.h>

struct s3c2440_timer_priv {
	unsigned int index;
	struct clk *pclk;
	struct clk *clk_gpio;
	struct clk *clk_timers;
	unsigned int irq;
	unsigned int start_bit;
	unsigned int manual_update_bit;
	unsigned int auto_reload_bit;
	unsigned int no_tout;
	unsigned int prescaler;
	unsigned int mux;
	timer_callback_t fn_callback;
};

static struct s3c2440_timer_priv s3c2440_timer0_priv = {
	.index = 0,
	.no_tout = 1,
};

static struct s3c2440_timer_priv s3c2440_timer1_priv = {
	.index = 1,
	.no_tout = 1,
};

static struct s3c2440_timer_priv s3c2440_timer2_priv = {
	.index = 2,
	.no_tout = 1,
};

static struct s3c2440_timer_priv s3c2440_timer3_priv = {
	.index = 3,
	.no_tout = 1,
};

static struct s3c2440_timer_priv s3c2440_timer4_priv = {
	.index = 4,
	.no_tout = 1,
};

static struct irqaction s3c2440_timer_irqaction;

static unsigned int s3c2440_timer_prescalers[2] = CONFIG_TIMER_PRESCALERS;

static unsigned int s3c2440_timer_muxs[5] = CONFIG_TIMER_MUXS;

static irqreturn_t s3c2440_timer_irq(unsigned int irq, void *_priv)
{
	struct timer *tm = _priv;
	struct s3c2440_timer_priv *priv = tm->priv;
	
	if (priv->fn_callback) {
		priv->fn_callback();
	}
	
	return IRQ_HANDLED;
}

static int s3c2440_timer_startup(struct timer *tm)
{
	struct s3c2440_timers *timer_regs;
	struct s3c2440_gpio *gpio = s3c2440_get_base_gpio();
	struct s3c2440_timer_priv *priv = tm->priv;
	struct clk *clk;
	int ret, i;
	uint32_t tmp, tmp1;

	if (!tm || IS_ERR(tm) || (priv->index >= 5))
		return -ENODEV;
	
	tm->membase = s3c2440_get_base_timers();
	timer_regs = (struct s3c2440_timers *)tm->membase;
	
	switch (priv->index) {
		case 0:
			priv->start_bit = 0;
			priv->manual_update_bit = 1;
			priv->auto_reload_bit = 3;
			break;
			
		case 4:
			priv->start_bit = 20;
			priv->manual_update_bit = 21;
			priv->auto_reload_bit = 22;
			break;
			
		default:
			priv->start_bit = 8 + 4 * (priv->index - 1);
			priv->manual_update_bit = priv->start_bit + 1;
			priv->auto_reload_bit = priv->start_bit + 3;
			break;
	}
	
	priv->fn_callback = NULL;

	clk = clk_get("pclk");
	ret = clk_enable(clk);
	if (IS_ERR(ret)) 
		return ret;
	priv->pclk = clk;

	clk = clk_get("gpio");
	ret = clk_enable(clk);
	if (IS_ERR(ret)) 
		goto err_clk_gpio;
	priv->clk_gpio = clk;

	clk = clk_get("timers");
	ret = clk_enable(clk);
	if (IS_ERR(ret)) 
		goto err_clk_timers;
	priv->clk_timers = clk;		

	if ((priv->index != 4) && !priv->no_tout) {
		tmp  = readl(&gpio->gpbcon) & ~(0x3 << (2 *priv->index));
		tmp |= (0x2 << (2 * priv->index));
		writel(tmp, &gpio->gpbcon);
		tmp  = readl(&gpio->gpbup) & ~(1 << priv->index);
		writel(tmp, &gpio->gpbup);
	}

	if ((s3c2440_timer_prescalers[0] < 1) || (s3c2440_timer_prescalers[1] < 1))
		goto err_setup_regs;
		
	if (priv->index <= 1) {
		priv->prescaler = s3c2440_timer_prescalers[0];
	} else {
		priv->prescaler = s3c2440_timer_prescalers[1];
	}
	priv->mux = s3c2440_timer_muxs[priv->index];

	writel(((s3c2440_timer_prescalers[1] - 1)<< 8) | ((s3c2440_timer_prescalers[0] - 1) << 0), timer_regs->tcfg0);
	
	tmp1 = 0;
	for (i = 0; i < 5; i++) {
		tmp = s3c2440_timer_muxs[i];
		if ((tmp != 2) || (tmp != 4) || (tmp != 8) || (tmp != 16))
			goto err_setup_regs;
		tmp = ffs(tmp) - 1;
		tmp1 |= (tmp << (4 * i));
	}
	writel(tmp1, timer_regs->tcfg1);
	writel(0, timer_regs->tcon);
	
	priv->irq = IRQ_TIMER0 + priv->index;
	ret = request_irq(priv->irq, &s3c2440_timer_irqaction, s3c2440_timer_irq, 0, tm);
	if (IS_ERR(ret)) 
		goto err_irq;
	
	return 0;

err_irq:
err_setup_regs:
	clk_disable(priv->clk_timers);
err_clk_timers:
	clk_disable(priv->clk_gpio);
err_clk_gpio:
	clk_disable(priv->pclk);

	return ret;
}

static void s3c2440_timer_shutdown(struct timer *tm)
{
    struct s3c2440_timer_priv *priv = tm->priv;

	if (!tm || IS_ERR(tm))
		return;
		
	free_irq(priv->irq, tm);
    clk_disable(priv->clk_timers);
    clk_disable(priv->clk_gpio);
    clk_disable(priv->pclk);      
}

static int s3c2440_timer_start(struct timer *tm)
{
	struct s3c2440_timer_priv *priv = tm->priv;
	struct s3c2440_timers *timer_regs = (struct s3c2440_timers *)tm->membase;
	uint32_t tmp;

	if (!tm || IS_ERR(tm))
		return -EINVAL;
		
	tmp  = readl(&timer_regs->tcon);
	tmp |= 1 << priv->start_bit;
	writel(tmp, &timer_regs->tcon);
	
	return 0;
}

static int s3c2440_timer_stop(struct timer *tm)
{
	struct s3c2440_timer_priv *priv = tm->priv;
	struct s3c2440_timers *timer_regs = (struct s3c2440_timers *)tm->membase;
	uint32_t tmp;

	if (!tm || IS_ERR(tm))
		return -EINVAL;

	tmp  = readl(&timer_regs->tcon);
	tmp &= ~(1 << priv->start_bit);
	writel(tmp, &timer_regs->tcon);
	
	return 0;
}

static int s3c2440_timer_set_type(struct timer *tm, unsigned int type)
{
	struct s3c2440_timer_priv *priv = tm->priv;
	struct s3c2440_timers *timer_regs = (struct s3c2440_timers *)tm->membase;
	uint32_t tmp;

	if (!tm || IS_ERR(tm))
		return -EINVAL;

	tmp = readl(&timer_regs->tcon);
	if (type == TIMER_TYPE_AUTORELOAD) {
		tmp |=  (1 << priv->auto_reload_bit);
	} else {
		tmp &= ~(1 << priv->auto_reload_bit);
	}
	writel(tmp, &timer_regs->tcon);
	
	return 0;
}

static int s3c2440_timer_get_type(struct timer *tm)
{
	struct s3c2440_timer_priv *priv = tm->priv;
	struct s3c2440_timers *timer_regs = (struct s3c2440_timers *)tm->membase;
	uint32_t tmp;

	if (!tm || IS_ERR(tm))
		return -EINVAL;

	tmp = readl(&timer_regs->tcon);
	tmp = tmp & (1 << priv->auto_reload_bit);
	return (tmp)? TIMER_TYPE_AUTORELOAD: TIMER_TYPE_ONESHOT;
}

static int s3c2440_timer_set_interval(struct timer *tm, uint64_t interval)
{
	struct s3c2440_timer_priv *priv = tm->priv;
	struct s3c2440_timers *timer_regs = (struct s3c2440_timers *)tm->membase;
	uint64_t tmp64, tmp64_1;

	if (!tm || IS_ERR(tm))
		return -EINVAL;

	tmp64   = clk_get_rate(priv->pclk);
	tmp64   = tmp64 * interval;
	tmp64_1 = 1000000000UL * priv->prescaler * priv->mux;
	tmp64   = div64_u64(tmp64, tmp64_1) - 1;
	
	if ((tmp64 > USHRT_MAX) || (tmp64 == 0)) 
		return -EFAULT;

	if (priv->index != 4) {
		writel((uint16_t)tmp64, &timer_regs->ch[priv->index].tcntb);
	} else {
		writel((uint16_t)tmp64, &timer_regs->tcntb4);
	}
	writel((1 << priv->manual_update_bit), &timer_regs->tcon);
	
	return (tmp64 + 1);
}

static int64_t s3c2440_timer_get_interval(struct timer *tm)
{
	struct s3c2440_timer_priv *priv = tm->priv;
	struct s3c2440_timers *timer_regs = (struct s3c2440_timers *)tm->membase;
	uint32_t tmp, rem;
	uint64_t tmp64;

	if (!tm || IS_ERR(tm))
		return -EINVAL;

		
	if (priv->index != 4) {
		tmp  = readl(&timer_regs->ch[priv->index].tcntb) + 1;
	} else {
		tmp  = readl(&timer_regs->tcntb4) + 1;
	}
	
	tmp64 = tmp * 1000000000UL * priv->prescaler * priv->mux;
	tmp64 = div_u64_rem(tmp64, clk_get_rate(priv->pclk), &rem);

	return tmp64;
}

static int64_t s3c2440_timer_get_time(struct timer *tm)
{
	struct s3c2440_timer_priv *priv = tm->priv;
	struct s3c2440_timers *timer_regs = (struct s3c2440_timers *)tm->membase;
	uint32_t tmp, rem;
	uint64_t tmp64;

	if (!tm || IS_ERR(tm))
		return -EINVAL;

	if (priv->index != 4) {
		tmp  = readl(&timer_regs->ch[priv->index].tcntb);
		tmp -= readl(&timer_regs->ch[priv->index].tcnto);
	} else {
		tmp  = readl(&timer_regs->tcntb4);
		tmp -= readl(&timer_regs->tcnto4);
	}

	tmp64 = tmp * 1000000000UL * priv->prescaler * priv->mux;
	tmp64 = div_u64_rem(tmp64, clk_get_rate(priv->pclk), &rem);

	return tmp64;
}

static int s3c2440_timer_set_callback(struct timer *tm, timer_callback_t callback)
{
	struct s3c2440_timer_priv *priv = tm->priv;
	
	if (!tm || IS_ERR(tm))
		return -EINVAL;

	priv->fn_callback = callback;
	
	return 0;
}
	
static struct timer_operations s3c2440_timer_ops = {
	.startup		= s3c2440_timer_startup,
	.shutdown		= s3c2440_timer_shutdown,
	.start			= s3c2440_timer_start,
	.stop			= s3c2440_timer_stop,
	.set_interval	= s3c2440_timer_set_interval,
	.get_interval	= s3c2440_timer_get_interval,
	.set_type		= s3c2440_timer_set_type,
	.get_type		= s3c2440_timer_get_type,
	.set_callback	= s3c2440_timer_set_callback,
	.get_time		= s3c2440_timer_get_time,
};

static struct timer s3c2440_timer0;

DEVICE(s3c2440_timer0_device, "timer0", MKDEV(MAJOR_TIMER, 0), 0, &s3c2440_timer0);

static struct timer s3c2440_timer0 = {
	.dev = &s3c2440_timer0_device,
	.priv = &s3c2440_timer0_priv,
};

static struct timer s3c2440_timer1;

DEVICE(s3c2440_timer1_device, "timer1", MKDEV(MAJOR_TIMER, 1), 0, &s3c2440_timer1);

static struct timer s3c2440_timer1 = {
	.dev = &s3c2440_timer1_device,
	.priv = &s3c2440_timer1_priv,
};

static struct timer s3c2440_timer2;

DEVICE(s3c2440_timer2_device, "timer2", MKDEV(MAJOR_TIMER, 2), 0, &s3c2440_timer2);

static struct timer s3c2440_timer2 = {
	.dev = &s3c2440_timer2_device,
	.priv = &s3c2440_timer2_priv,
};

static struct timer s3c2440_timer3;

DEVICE(s3c2440_timer3_device, "timer3", MKDEV(MAJOR_TIMER, 3), 0, &s3c2440_timer3);

static struct timer s3c2440_timer3 = {
	.dev = &s3c2440_timer3_device,
	.priv = &s3c2440_timer3_priv,
};

static struct timer s3c2440_timer4;

DEVICE(s3c2440_timer4_device, "timer4", MKDEV(MAJOR_TIMER, 4), 0, &s3c2440_timer4);

static struct timer s3c2440_timer4 = {
	.dev = &s3c2440_timer4_device,
	.priv = &s3c2440_timer4_priv,
};

static struct timer_driver s3c2440_timer_timer_driver;

DRIVER(s3c2440_timer_driver, NULL, MKDEV(MAJOR_TIMER, 0), MKDEV(MAJOR_TIMER, 4), 
	&timer_ops, &s3c2440_timer_timer_driver);

static struct timer_driver s3c2440_timer_timer_driver = {
	.drv = &s3c2440_timer_driver,
	.ops = &s3c2440_timer_ops,
};

